import os
import time
import pandas
import imaplib
import mammoth
import threading
import tkinter as tk
from email import encoders
from datetime import datetime
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from tkinter import filedialog, messagebox, ttk


class SaveAliEmail:

    def __init__(self):
        self.windows = tk.Tk()
        self.windows.geometry('400x450')
        self.windows.title('阿里企业邮箱批量保存草稿')
        self.file_paths = {
            '*.docx':None,
            '*.xls*':None,
            '*.*':None
        }
        self.pwd = None
        self.email = None
        self.is_cancel = False
        self.confirm_button_staut = 0
        self.status_title = '未开始'
        self.headers = ['收件人(多个用，隔开)', '抄送(多个用，隔开)', '称谓', '主题']
        self.create_entry()
        self.upload_button("*请上传word", '*.docx')
        self.upload_button("*请上传excel", '*.xls*')
        self.upload_button("请上传附件", '*.*')
        self.download_excle_button()
        self.create_confirm_button()
        self.create_progress()

    def on_confirm_button_click(self):
        '''点击按钮事件处理,如果confirm_button_staut=0是确定开始运行并更新按钮状态，1是取消'''
        if self.confirm_button_staut == 0:
            # 数据校验通过后才可继续执行后续操作,执行完操作后更新按钮状态并更新按钮样式
            if self.check_data():
                self.is_cancel = False
                self.status_title = '执行中... '
                thread = threading.Thread(target=self.run_threding)
                thread.start()
                self.confirm_button_staut = 1
                self.update_confirm_button()
        else:
            # 取消需二次确认,取消后更新按钮状态
            if messagebox.askyesno('取消', '是否确认取消'):
                self.is_cancel = True
                self.status_title = '已取消'
                self.confirm_button_staut=0
                self.update_confirm_button()

    def run_threding(self):
        '''创建执行操作线程，避免主线程阻塞'''
        excel_data = self.read_excel(self.file_paths['*.xls*'])
        value = 0
        for i in excel_data:
            value += 1
            # 判断是否取消，非取消才会执行操作
            if not self.is_cancel:
                body = self.word_to_html(self.file_paths['*.docx'], i[2])
                self.save_email(
                    cc=i[1],
                    body=body,
                    subject=i[3],
                    recipients=i[0],
                    pwd=self.pwd,
                    email=self.email,
                    attachment=self.file_paths['*.*']
                )
                # 使用after方法在主线程中更新进度条，直接调用不会生效
                self.windows.after(0, self.update_progress, value, len(excel_data))
                if self.progress['value'] >= 100:  # 运行结束后更新按钮
                    self.status_title = '已完成'
                    self.windows.after(0, self.update_progress, value, len(excel_data))
                    self.confirm_button_staut = 0
                    self.windows.after(0, self.update_confirm_button)

    def create_entry(self):
        '''创建邮箱和密码输入框'''
        tk.Label(self.windows, text='*请输入阿里企业邮箱：').pack()
        self.email_entry = tk.Entry(self.windows, width=25)
        self.email_entry.pack()
        tk.Label(self.windows, text='*请输入密码：').pack()
        self.pwd_entry = tk.Entry(self.windows, width=25)
        self.pwd_entry.pack()

    def upload_button(self, text, file_type):
        '''创建上传按钮和文件路径标题，并将file_type设置成动态变量赋值为file_label（以便后面动态修改对应的路径标题）'''
        upload_button = tk.Button(
            self.windows,
            command=lambda k=file_type:self.upload_file(k),
            bg="#2196F3",
            fg="white",
            text=text,
            width=15,
            height=1
        )
        upload_button.pack(pady=5)
        file_label = tk.Label(self.windows, text='未选择文件')
        file_label.pack()
        setattr(self, file_type, file_label)

    def upload_file(self, file_type):
        '''上传文件，file_type限制上传文件类型(*.xls)，上传后将路径赋值到file_paths，并将标题设置为文件路径'''
        file_path = filedialog.askopenfilename(filetypes=[('文件',file_type)])
        if file_path:
            self.file_paths[file_type] = file_path
            file_path_label = getattr(self, file_type)
            file_path_label.config(text=file_path, fg='green')

    def create_confirm_button(self):
        '''创建确认按钮，点击后进行数据校验'''
        self.confirm_button = tk.Button(
            self.windows,
            text="确认",
            command= self.on_confirm_button_click,
            width=15,
            height=1,
            bg="#4CAF50",
            fg="white"
        )
        self.confirm_button.pack()

    def update_confirm_button(self):
        '''更新按钮按钮文案和背景色'''
        if self.confirm_button_staut==1:
            self.confirm_button.config(text='取消', bg='#ed3155')
        else:
            self.confirm_button.config(text='确定', bg="#4CAF50")

    def check_data(self):
        '''校验除附件外数据必填，数据不完整则弹窗提示，数据完整再校验邮箱密码是否正确和excel是否为空，return布尔值用于判断是否执行后续操作'''
        miss_files = [k for k, v in self.file_paths.items() if not v and k!='*.*']
        email = self.email_entry.get() #获取输入邮箱
        pwd = self.pwd_entry.get() #获取输入密码
        if miss_files or not email or not pwd:
            messagebox.showerror(message='请输入邮箱和密码，并上传除了附件以外的文件')
            return False
        else:
            self.email = email
            self.pwd = pwd
            try:
                with imaplib.IMAP4_SSL('imap.qiye.aliyun.com', 993) as mail:
                    mail.login(email, pwd)
                is_login = True
            except:
                messagebox.showerror(title='登录失败', message="邮箱或密码错误，请核对")
                is_login = False
            excel_data = self.read_excel(self.file_paths['*.xls*'])
            if not excel_data:
                messagebox.showerror(title='excel错误', message='excel模板为空，请核对')
            if is_login and excel_data:
                return True
            else:
                return False

    def create_progress(self):
        '''创建带百分比的进度条'''
        self.progress = ttk.Progressbar(self.windows, length=200, maximum=100)
        self.progress.pack(pady=10)
        self.progress_label = tk.Label(self.windows, text=self.status_title)
        self.progress_label.pack()

    def update_progress(self, value, total):
        '''更新进度条和进度条下的提示文案，value是当前进度值，total是总量'''
        percent = round(value/total*100, 2)
        self.progress["value"] = percent
        self.progress_label.config(text=f"{self.status_title}:   {percent}%    {value}/{total}")

    def read_excel(self, excel_path):
        '''逐行读取excel每行为一个列表，最后放到一个总的列表,先校验表头是否正确否则弹窗提示'''
        data_list = []
        dtype_dict = {col: str for col in self.headers}#将int转成str
        excel_data = pandas.read_excel(excel_path, dtype=dtype_dict)
        first_row = excel_data.columns.tolist()
        if first_row!=self.headers:
            messagebox.showerror(title='错误', message="上传的excel模板有误，请下载新模板重新上传")
        excel_data = excel_data.fillna('')#将空的单元格填充'',不可为空下方校验需用到字符串
        excel_data = excel_data.replace('\n', '', regex=True)  # 将换行符替换为空格,regex是否用正则表达
        #如有多个抄送人/收件人则按，分隔成多个抄送人的列表
        excel_data[self.headers[0]]=excel_data[self.headers[0]].str.split(r'[,，]')
        excel_data[self.headers[1]]=excel_data[self.headers[1]].str.split(r'[,，]')
        for row in excel_data.itertuples(index=False, name=None):
            data_list.append(list(row))
        return data_list

    def word_to_html(self, docx_path, name):
        '''将word转成html并保持表格宽度不变，并将文件中的${NAME}替换成name'''
        with open(docx_path, "rb") as docx_file:
            result = mammoth.convert_to_html(
                docx_file,
                convert_image=mammoth.images.img_element(lambda image: {"src": image.alt}),
                style_map="""table[style-name='Table Grid'] => table.table(border='1')""")
            html = f"""<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Word to HTML</title><style>table {{border-collapse: collapse;table-layout: fixed; /* 固定表格布局 */width: auto; /* 自动宽度 */height: auto; /* 自动高度 */}}table, th, td {{border: 1px solid black;}}th, td {{padding: 8px;text-align: left;height: auto; /* 单元格高度自动 */white-space: nowrap; /* 防止文本换行改变高度 */}}/* 保留Word中的行高设置 */p {{margin: 0;line-height: 1.5; /* 保持行高，可根据需要调整 */}}</style></head><body>{result.value}</body></html>"""
        html = str(html).replace('${NAME}', name)
        return html

    def download_excle_button(self):
        '''创建下载excel模板按钮'''
        download_excle_button = tk.Button(
            self.windows,
            command=self.download_excle,
            bg="#4CAF50",
            fg="white",
            text='下载excel模板',
            width=15,
            height=1
        )
        download_excle_button.pack(pady=15)

    def download_excle(self):
        '''下载excle模板到当前目录下'''
        df = pandas.DataFrame(columns=self.headers)
        excel_name = '\excel模板' + datetime.now().strftime('%m%d%H%M%S') + '.xlsx'
        excel_path = f'{os.getcwd()}{excel_name}'
        df.to_excel(excel_path, index=False)
        messagebox.showinfo('提示', f'excel模板下载成功！文件路径：{excel_path}')

    def save_email(self, email, pwd, subject, body, recipients=None, attachment=None, cc=None):
        """使用IMAP保存阿里邮箱草稿，recipients收件人列表，attachment附件路径列表
        cc抄送人列表"""
        try:
            msg = MIMEMultipart()
            msg['From'] = email
            msg['Subject'] = subject
            msg.attach(MIMEText(body, 'html', 'utf-8'))
            # 设置收件人、抄送
            if recipients: msg['To'] = ', '.join(recipients)
            if cc: msg['Cc'] = ', '.join(cc)
            # 添加附件
            if attachment:
                with open(attachment, "rb") as file:
                    part = MIMEBase("application", "octet-stream")# 创建附件部分
                    part.set_payload(file.read())
                    encoders.encode_base64(part)  # 必须Base64编码
                    filename = os.path.basename(attachment)# 处理文件名（防止中文乱码）
                    part.add_header('Content-Disposition', 'attachment',
                               filename=('utf-8', '', filename))
                    msg.attach(part)
            # 连接服务器并保存
            with imaplib.IMAP4_SSL('imap.qiye.aliyun.com', 993) as mail:
                mail.login(email, pwd)#登录
                mail.select('Drafts')# 选择草稿箱
                mail.append(
                    'Drafts','',
                    imaplib.Time2Internaldate(datetime.now().timestamp()),
                    str(msg).encode('utf-8')
                )
                print("草稿保存成功")
        except Exception as e:
            messagebox.showerror('未知错误', message=f'发生未知错误，请取消重试！，报错信息：{e}')

if __name__ == '__main__':
    SaveAliEmail = SaveAliEmail()
    SaveAliEmail.windows.mainloop()
